package lion.dev.image;

import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;

import org.apache.commons.lang.ArrayUtils;

import com.jhlabs.image.ScaleFilter;
import com.jhlabs.image.SharpenFilter;

/**
 * 默认的图片处理，对于单张 JPEG,PNG,BMP都可以完美解决
 *
 * + 部分png图片格式为 TYPE_BYTE_INDEXED，将其转为TYPE_INT_RGB可解决其中出现的反色图片问题
 *
 *
 * @author helin
 * @email hl_0074@sina.com
 * @date 2016年8月16日
 */
public class DefaultImageHandler implements IImageHandler {

	private static int[] SPECILA_IMAGE_TYPES = new int[] { BufferedImage.TYPE_BYTE_INDEXED,
			BufferedImage.TYPE_BYTE_BINARY, BufferedImage.TYPE_3BYTE_BGR, BufferedImage.TYPE_INT_BGR };

	private BufferedImage image;

	@Override
	public void read(InputStream src) throws IOException {

		this.image = ImageIO.read(src);
	}

	@Override
	public void read(BufferedImage src) {

		this.image = src;
	}

	public DefaultImageHandler() {

	}

	public DefaultImageHandler(BufferedImage src) {

		this.image = src;
	}

	@Override
	public IImageHandler compress(float quality, String type) {

		BufferedImage result = compress(this.image, quality, type);

		return new DefaultImageHandler(result);
	}

	public static BufferedImage compress(BufferedImage image, float quality, String type) {

		Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName(type);// 得到迭代器
		if (!iter.hasNext()) { return image; } // not compress

		ImageWriter writer = iter.next(); // 得到writer
		// 得到指定writer的输出参数设置(ImageWriteParam )
		ImageWriteParam iwp = writer.getDefaultWriteParam();
		if (!iwp.canWriteCompressed()) { return image; } // 不支持压缩

		BufferedImage result = null;
		try {
			iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // 设置可否压缩

			if (iwp.canWriteProgressive()) {// 是否支持质量递增传递的逐步序列的方式写入图像
				iwp.setProgressiveMode(ImageWriteParam.MODE_DISABLED); // 如果支持，则禁用，默认为MODE_COPY_FROM_METADATA
			}

			if (iwp.getCompressionTypes().length > 0) {
				iwp.setCompressionType(iwp.getCompressionTypes()[0]);
			}
			iwp.setCompressionQuality(quality); // 设置压缩质量参数
			IIOImage iImage = new IIOImage(image, null, null);
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			ImageOutputStream ios = ImageIO.createImageOutputStream(baos);
			writer.setOutput(ios);
			writer.write(null, iImage, iwp);
			ios.flush(); // important
			writer.dispose();
			result = ImageIO.read(new ByteArrayInputStream(baos.toByteArray()));
		} catch (IOException e) {
			e.printStackTrace();
			result = image;
		}
		return result;
	}

	@Override
	public IImageHandler resize(int width, int height) {

		BufferedImage result = resize(this.image, width, height);
		result = sharpen(result, 1);

		return new DefaultImageHandler(result);
	}

	public static BufferedImage resize(BufferedImage src, int width, int height) {

		return new ScaleFilter(width, height).filter(src, null);
	}

	@Override
	public IImageHandler resizeWidth(int width) {

		int height = (int) (this.image.getHeight() * (width * 1.0 / this.image.getWidth()));
		return this.resize(width, height);
	}

	@Override
	public IImageHandler resizeHeight(int height) {

		int width = (int) (this.image.getWidth() * (height * 1.0 / this.image.getHeight()));
		return this.resize(width, height);
	}

	@Override
	public IImageHandler resizeScale(float percent) {

		int width = (int) (this.image.getWidth() * percent);
		int height = (int) (this.image.getHeight() * percent);

		return this.resize(width, height);
	}

	@Override
	public void write(OutputStream os, String type) throws IOException {

		FileFormatWriter writer = FileFormatWriterManager.getFormatWriter(type);
		if (writer == null) { throw new IOException("unsupported file format " + type); }

		BufferedImage result = checkImageType(this.image);

		writer.writeImage(os, result);
	}

	@Override
	public IImageHandler rotate(Rotation rotation) {

		BufferedImage result = rotate(this.image, rotation);

		return new DefaultImageHandler(result);
	}

	public static BufferedImage rotate(BufferedImage src, Rotation rotation) {

		AffineTransform tx = new AffineTransform();

		int newWidth = src.getWidth();
		int newHeight = src.getHeight();
		switch (rotation) {
		case CW_90:
			newWidth = src.getHeight();
			newHeight = src.getWidth();

			tx.translate(newWidth, 0.0D);
			tx.rotate(Math.toRadians(90.0D));

			break;
		case CW_270:
			newWidth = src.getHeight();
			newHeight = src.getWidth();

			tx.translate(0.0D, newHeight);
			tx.rotate(Math.toRadians(-90.0D));
			break;
		case CW_180:
			tx.translate(newWidth, newHeight);
			tx.rotate(Math.toRadians(180.0D));
			break;
		case FLIP_HORZ:
			tx.translate(newWidth, 0.0D);
			tx.scale(-1.0D, 1.0D);
			break;
		case FLIP_VERT:
			tx.translate(0.0D, newHeight);
			tx.scale(1.0D, -1.0D);
		}

		BufferedImage result = new BufferedImage(newWidth, newHeight, getType(src.getType()));
		Graphics2D g2 = result.createGraphics();
		g2.drawImage(src, tx, null);
		g2.dispose();

		return result;
	}

	public static BufferedImage crop(BufferedImage src, int x, int y, int width, int height) {

		BufferedImage result = new BufferedImage(width, height, getType(src.getType()));
		Graphics2D g2 = result.createGraphics();
		g2.drawImage(src, 0, 0, width, height, x, y, x + width, y + height, null);
		g2.dispose();

		return result;
	}

	@Override
	public IImageHandler crop(int x, int y, int width, int height) {

		BufferedImage result = crop(this.image, x, y, width, height);

		return new DefaultImageHandler(result);
	}

	@Override
	public IImageHandler watermark(BufferedImage watermark, Directoin directoin, float opacity) {

		// 计算坐标
		BufferedImage result = merge(this.image, watermark, directoin, opacity);
		return new DefaultImageHandler(result);
	}

	@Override
	public IImageHandler watermark(BufferedImage watermark, int x, int y, float opacity) {

		BufferedImage result = merge(this.image, watermark, x, y, opacity);
		return new DefaultImageHandler(result);
	}

	@Override
	public IImageHandler sharpen(int times) {

		BufferedImage result = sharpen(this.image, times);
		return new DefaultImageHandler(result);
	}

	public static BufferedImage sharpen(BufferedImage image, int times) {

		SharpenFilter filter = new SharpenFilter();
		for (int i = 0; i < times; i++) {
			image = filter.filter(image, null);
		}
		return image;
	}

	public static BufferedImage merge(BufferedImage src, BufferedImage watermark, Directoin directoin, float opacity) {

		int x = 0, y = 0;
		switch (directoin) {
		case TL:
			x = 0;
			y = 0;
			break;
		case BL:
			x = 0;
			y = src.getHeight() - watermark.getHeight();
			break;
		case TR:
			x = src.getWidth() - watermark.getWidth();
			y = 0;
			break;
		case BR:
			x = src.getWidth() - watermark.getWidth();
			y = src.getHeight() - watermark.getHeight();
			break;
		case CENTER:
			x = (src.getWidth() - watermark.getWidth()) / 2;
			y = (src.getHeight() - watermark.getHeight()) / 2;
		}

		return merge(src, watermark, x, y, opacity);
	}

	public static BufferedImage merge(BufferedImage src, BufferedImage watermark, int x, int y, float opacity) {

		BufferedImage result = new BufferedImage(src.getWidth(), src.getHeight(), getType(src.getType()));
		Graphics2D g2 = (Graphics2D) result.getGraphics();
		g2.drawImage(src, 0, 0, null);

		if (opacity > 0) {
			g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, opacity));
		}
		g2.drawImage(watermark, x, y, null);
		if (opacity > 0) {
			g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
		}

		g2.dispose();

		return result;
	}

	public static int getType(int type) {

		if (ArrayUtils.contains(SPECILA_IMAGE_TYPES, type)) { return BufferedImage.TYPE_INT_ARGB; }
		return type;
	}

	private BufferedImage checkImageType(BufferedImage src) {

		if (src.getType() != BufferedImage.TYPE_BYTE_INDEXED || src.getType() == BufferedImage.TYPE_BYTE_BINARY) { return src; }

		BufferedImage result = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_RGB);

		Graphics2D g2 = result.createGraphics();
		g2.setComposite(AlphaComposite.Src);
		g2.drawImage(src, 0, 0, null);
		g2.dispose();

		return result;
	}

	@Override
	public IImageHandler limit(int maxwidth, int maxheight) {

		BufferedImage result = limit(this.image, maxwidth, maxheight);
		return new DefaultImageHandler(result);
	}

	public static BufferedImage limit(BufferedImage image, int maxwidth, int maxheight) {

		int width = image.getWidth();
		int height = image.getHeight();
		if (maxwidth > 0 && width > maxwidth) {
			height = (int) (height * (maxwidth * 1f / width));
			width = maxwidth;
		}
		if (maxheight > 0 && height > maxheight) {
			width = (int) (width * (height * 1f / maxheight));
			height = maxheight;
		}
		return resize(image, width, height);
	}
}
